using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace PrisonStep
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class PrisonGame : Microsoft.Xna.Framework.Game
    {
        #region Fields

        /// <summary>
        /// This graphics device we are drawing on in this assignment
        /// </summary>
        GraphicsDeviceManager graphics;

        SpriteBatch mSpriteBatch;

        public int UniversalDoor;
        
        /// <summary>
        /// The camera we use
        /// </summary>
        private Camera camera;

        /// <summary>
        /// The player in your game is modeled with this class
        /// </summary>
        private Player player;

        /// <summary>
        /// This is the actual model we are using for the prison
        /// </summary>
        private List<PrisonModel> phibesModel = new List<PrisonModel>();

        /// <summary>
        /// Our instance of line draw.
        /// </summary>
        private PSLineDraw mLineDraw;

        private AlienModel mAlien;

        private MouseTrack mouseTrack;

        private Gort mGort;

        private Dalek mDalek;

        private int mScore = 0;

        private SpriteFont mScoreFont;

        private bool mSlimed = false;

        private const float mSlimeTime = 1.0f;

        private float mSlimeWeight = 1.0f;

        public string HitString; 
		
        private ParticleSystem mSmokePlume;

        /// <summary>
        /// This dictionary captures the name and the list of
        /// the triangles for a region.
        /// </summary>
        private Dictionary<string, List<Vector2>> regions = new Dictionary<string, List<Vector2>>();

        private double[] lightData =
        {   1,      568,      246,    1036,   0.53,   0.53,   0.53,     821,     224, 
          941,  14.2941,       45, 43.9412,    814,    224,   1275,    82.5,       0,  0,
            2,       -5,      169,     428, 0.3964,  0.503, 0.4044,    -5.4,     169,
         1020, 129.4902, 107.5686, 41.8039,   -5.4,    169,   -138, 37.8275,      91, 91,
            3,      113,      217,    -933,    0.5,      0,      0,    -129,     185,
        -1085,	     50,        0,       0,    501,    185,  -1087,      48,       0,  0,
            4,      781,      209,    -998,    0.2, 0.1678, 0.1341,    1183,     209,
         -998,	     50,  41.9608, 33.5294,    984,    113,   -932,       0,      80,  0,
            5,      782,      177,    -463,   0.65, 0.5455, 0.4359,     563,     195,
         -197,	     50,        0,       0,   1018,    181,   -188,      80,       0,  0,
            6,     1182,      177,   -1577,   0.65, 0.5455, 0.4359,     971,     181,
        -1801,        0,  13.1765,      80,   1406,    181,  -1801,       0, 13.1765,  80};

        #endregion

        #region Properties


        public int Score { get { return mScore; } set { mScore = value; } }
        /// <summary>
        /// The game camera
        /// </summary>
        public Camera Camera { get { return camera; } }

        /// <summary>
        /// The list of the prison models.
        /// </summary>
        public List<PrisonModel> Models { get { return phibesModel; } set { phibesModel = value; } }

        public PSLineDraw LineDraw { get { return mLineDraw; } }

        public float SlimeWeight { get { return mSlimeWeight; } set { mSlimeWeight = value; } }

        public Player Victoria { get { return player; } }

        public AlienModel Alien { get { return mAlien; } }

        public Gort GortModel { get { return mGort; } }

        public Dalek DalekModel { get { return mDalek; } }

        public bool Slimed { get { return mSlimed; } set { mSlimed = value; } }
        #endregion


        /// <summary>
        /// Constructor
        /// </summary>
        public PrisonGame()
        {
            // XNA startup
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            // Create objects for the parts of the ship
            for(int i=1;  i<=6;  i++)
            {
                phibesModel.Add(new PrisonModel(this, i));
            }

            // Create a player object
            player = new Player(this);

            // Some basic setup for the display window
            this.IsMouseVisible = false;
			this.Window.AllowUserResizing = true;
			this.graphics.PreferredBackBufferWidth = 1024;
			this.graphics.PreferredBackBufferHeight = 728;

            // Basic camera settings
            camera = new Camera(graphics);
            camera.FieldOfView = MathHelper.ToRadians(42);

            mLineDraw = new PSLineDraw(this, camera);
            this.Components.Add(mLineDraw);

            camera.Eye = new Vector3(800, 180, 1053);
            camera.Center = new Vector3(275, 90, 1053);

            //ParticleEffect
            mSmokePlume = new SmokePlumeParticleSystem(this, Content);


            mouseTrack = new MouseTrack(this);

            Components.Add(mSmokePlume);

            // Characters
            mAlien = new AlienModel(this);
            mGort = new Gort(this);
            mDalek = new Dalek(this);
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            camera.Initialize();

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            mSpriteBatch = new SpriteBatch(GraphicsDevice);

            mScoreFont = Content.Load<SpriteFont>("ScoreFont");

            player.LoadContent(Content);
            mAlien.LoadContent(Content);
            mGort.LoadContent(Content);
            mDalek.LoadContent(Content);
            mouseTrack.LoadContent(Content);

            foreach (PrisonModel model in phibesModel)
            {
                model.LoadContent(Content);
            }

            Model CollisionModel = Content.Load<Model>("AntonPhibesCollision");
            // Nothing for now...

            Matrix[] M = new Matrix[CollisionModel.Bones.Count];
            CollisionModel.CopyAbsoluteBoneTransformsTo(M);

            foreach (ModelMesh mesh in CollisionModel.Meshes)
            {
                // For accumulating the triangles for this mesh
                List<Vector2> triangles = new List<Vector2>();

                // Loop over the mesh parts
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                {

                    // 
                    // Obtain the vertices for the mesh part
                    //

                    int numVertices = meshPart.VertexBuffer.VertexCount;
                    VertexPositionColorTexture[] verticesRaw = new VertexPositionColorTexture[numVertices];
                    meshPart.VertexBuffer.GetData<VertexPositionColorTexture>(verticesRaw);

                    //
                    // Obtain the indices for the mesh part
                    //

                    int numIndices = meshPart.IndexBuffer.IndexCount;
                    short[] indices = new short[numIndices];
                    meshPart.IndexBuffer.GetData<short>(indices);


                    //
                    // Build the list of triangles
                    //

                    for (int i = 0; i < meshPart.PrimitiveCount * 3; i++)
                    {
                        // The actual index is relative to a supplied start position
                        int index = i + meshPart.StartIndex;

                        // Transform the vertex into world coordinates
                        Vector3 v = Vector3.Transform(verticesRaw[indices[index] + meshPart.VertexOffset].Position, M[mesh.ParentBone.Index]);
                        triangles.Add(new Vector2(v.X, v.Z));
                    }
                }

                regions[mesh.Name] = triangles;
            }
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }


        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            mLineDraw.Clear();

            mLineDraw.Crosshair(player.BazMat.Translation + player.BazMat.Backward * 200, 10, Color.Red);

            if (mSlimed)
            {
                SlimeWeight -= (float)(0.6 * (float)gameTime.ElapsedGameTime.TotalSeconds / mSlimeTime);
                if (SlimeWeight < 0.4f)
                    SlimeWeight = 0.4f;
            }
            else
            {
                SlimeWeight += (float)(0.6 * (float)gameTime.ElapsedGameTime.TotalSeconds / mSlimeTime);
                if (SlimeWeight > 1.0f)
                    SlimeWeight = 1.0f;
            }
            //System.Diagnostics.Trace.WriteLine(SlimeWeight);
            mAlien.SlimeWeight = SlimeWeight;
            mGort.SlimeWeight = SlimeWeight;
            mDalek.SlimeWeight = SlimeWeight;
            player.SlimeWeight = SlimeWeight;

            for (int i = 0; i < phibesModel.Count; i++)
            {
                phibesModel[i].SlimeWeight = SlimeWeight;
            }

            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
                Keyboard.GetState().IsKeyDown(Keys.Escape))
                this.Exit();

            //
            // Update game components
            //


            
            player.Update(gameTime);
            mAlien.Update(gameTime);
            mGort.Update(gameTime);
            mDalek.Update(gameTime);
            mouseTrack.Update(gameTime,mAlien.BoundingCyl, mGort.BoundingCyl, mDalek.BoundingCyl);

            if (Mouse.GetState().RightButton.ToString().CompareTo("Released") == -1)
            {

            }

            foreach (PrisonModel model in phibesModel)
            {
                model.Update(gameTime);
            }

            camera.Update(gameTime);

            mSmokePlume.AddParticle(Vector3.Zero, Vector3.Zero);

            base.Update(gameTime);
            
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(Color.Black);

            foreach (PrisonModel model in phibesModel)
            {
                model.Draw(graphics, gameTime, player.Region);
            }




           
            player.Draw(graphics, gameTime);
            mAlien.Draw(graphics, gameTime);
            mGort.Draw(graphics, gameTime);
            mDalek.Draw(graphics, gameTime);

            mSmokePlume.SetCamera(camera.View, camera.Projection);
            
			mSpriteBatch.Begin();
            if (Mouse.GetState().LeftButton.ToString().CompareTo("Released") == -1)
            {
                mSpriteBatch.DrawString(mScoreFont, HitString, new Vector2(100, 0), Color.White);
            }
            mSpriteBatch.DrawString(mScoreFont, "Score: " + mScore.ToString(), new Vector2(0, 0), Color.White);
            mSpriteBatch.End();
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
           

            base.Draw(gameTime);
        }


        /// <summary>
        /// Tests to see if a vector is inside a region of the collision floor plan.
        /// </summary>
        /// <param name="v3"></param>
        /// <returns></returns>
        public string TestRegionNoWall(Vector3 v3, ref Vector3 aNormal)
        {
            // Convert to a 2D Point
            float x = v3.X;
            float y = v3.Z;

            foreach (KeyValuePair<string, List<Vector2>> region in regions)
            {
                //Code not needed untill we started shooting pies since 
                // For now we ignore the walls
                if (region.Key.StartsWith("W"))
                    continue;

                for (int i = 0; i < region.Value.Count; i += 3)
                {
                    float x1 = region.Value[i].X;
                    float x2 = region.Value[i + 1].X;
                    float x3 = region.Value[i + 2].X;
                    float y1 = region.Value[i].Y;
                    float y2 = region.Value[i + 1].Y;
                    float y3 = region.Value[i + 2].Y;


                    //Computes the edge normal for the region.
                    // The final one will be the edge normal for that which we've intersected.
                    aNormal.X = y1 - y2;
                    aNormal.Y = 0;
                    aNormal.Z = x2 - x1;
                    aNormal.Normalize();


                    float d = 1.0f / ((x1 - x3) * (y2 - y3) - (x2 - x3) * (y1 - y3));
                    float l1 = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) * d;
                    if (l1 < 0)
                        continue;

                    float l2 = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) * d;
                    if (l2 < 0)
                        continue;

                    float l3 = 1 - l1 - l2;
                    if (l3 < 0)
                        continue;

                    return region.Key;
                }
            }

            return "";
        }

        public string TestRegionNoWall(Vector3 v3)
        {
            // Convert to a 2D Point
            float x = v3.X;
            float y = v3.Z;

            foreach (KeyValuePair<string, List<Vector2>> region in regions)
            {
                //Code not needed untill we started shooting pies since 
                // For now we ignore the walls
                if (region.Key.StartsWith("W"))
                    continue;

                for (int i = 0; i < region.Value.Count; i += 3)
                {
                    float x1 = region.Value[i].X;
                    float x2 = region.Value[i + 1].X;
                    float x3 = region.Value[i + 2].X;
                    float y1 = region.Value[i].Y;
                    float y2 = region.Value[i + 1].Y;
                    float y3 = region.Value[i + 2].Y;


                    //Computes the edge normal for the region.
                    // The final one will be the edge normal for that which we've intersected.

                    float d = 1.0f / ((x1 - x3) * (y2 - y3) - (x2 - x3) * (y1 - y3));
                    float l1 = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) * d;
                    if (l1 < 0)
                        continue;

                    float l2 = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) * d;
                    if (l2 < 0)
                        continue;

                    float l3 = 1 - l1 - l2;
                    if (l3 < 0)
                        continue;

                    return region.Key;
                }
            }

            return "";
        }
        /// <summary>
        /// Tests to see if the location is within a region.
        /// Also this function returns the vector normal of the collision that it has
        /// </summary>
        /// <param name="v3"></param>
        /// <param name="aNormal"></param>
        /// <returns></returns>
        public string TestRegion(Vector3 v3, ref Vector3 aNormal)
        {
            // Convert to a 2D Point
            float x = v3.X;
            float y = v3.Z;

            foreach (KeyValuePair<string, List<Vector2>> region in regions)
            {

                for (int i = 0; i < region.Value.Count; i += 3)
                {
                    float x1 = region.Value[i].X;
                    float x2 = region.Value[i + 1].X;
                    float x3 = region.Value[i + 2].X;
                    float y1 = region.Value[i].Y;
                    float y2 = region.Value[i + 1].Y;
                    float y3 = region.Value[i + 2].Y;


                    //Computes the edge normal for the region.
                    // The final one will be the edge normal for that which we've intersected.
                    aNormal.X = y1 - y2;
                    aNormal.Y = 0;
                    aNormal.Z = x2 - x1;
                    aNormal.Normalize();


                    float d = 1.0f / ((x1 - x3) * (y2 - y3) - (x2 - x3) * (y1 - y3));
                    float l1 = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) * d;
                    if (l1 < 0)
                        continue;

                    float l2 = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) * d;
                    if (l2 < 0)
                        continue;

                    float l3 = 1 - l1 - l2;
                    if (l3 < 0)
                        continue;

                    return region.Key;
                }
            }

            return "";
        }

        /// <summary>
        /// This function will be called upon collision and will allow for 
        /// sliding along the walls
        /// </summary>
        /// <param name="aCurrLoc"></param>
        /// <param name="aDesiredLoc"></param>
        /// <param name="aWallNormal"></param>
        /// <returns></returns>
        public Vector3 SlideOnWall(Vector3 aCurrLoc, Vector3 aDesiredLoc, Vector3 aWallNormal)
        {
            // First we find the vector of the player
            Vector3 lPlayerVect = aDesiredLoc - aCurrLoc;
            lPlayerVect.Normalize();

            //This will find the direction which will travel along in the
            Vector3 lSlideDirection = Vector3.Cross(new Vector3(0, 1, 0), aWallNormal);
            //lSlideDirection.Z = -lSlideDirection.Z;
            lSlideDirection.Normalize();

            // Find the angle between the two vectors
            float lDotProd = Vector3.Dot(lPlayerVect, lSlideDirection);

            if (lDotProd > 0)
            {
                return aCurrLoc + (lDotProd * lSlideDirection) - (0.01f * aWallNormal);

            }
            else if (lDotProd < 0)
            {
                return aCurrLoc - (lDotProd * lSlideDirection) + (0.01f * aWallNormal);
            }
            else
            {
                return aCurrLoc - (0.01f * aWallNormal);
            }
        }
	        public Vector3 LightInfo(int section, int item)
        {
            int offset = (section - 1) * 19 + 1 + (item * 3);
            return new Vector3((float)lightData[offset],
                               (float)lightData[offset + 1],
                               (float)lightData[offset + 2]);
        }

        /// <summary>
        /// This function can be used to determine what main region the 
        /// </summary>
        /// <param name="aRegion"></param>
        /// <returns></returns>
        public int GetRegion(string aRegion)
        {
            if (aRegion.StartsWith("R_Section"))
                return (int)Char.GetNumericValue(aRegion[aRegion.Length - 1]);
            else
                return -1;
        }
        public int GetDoorRegion(string aRegion)
        {
            if (aRegion.StartsWith("R_Door"))
                return (int)Char.GetNumericValue(aRegion[aRegion.Length - 1]);
            else
                return -1;
        }

        public int TestAdjacentRegion(int aLastRegion, int aDoorNum)
        {
            switch (aLastRegion)
            {
                case 1:
                    if (aDoorNum == 1)
                        return 2;
                    break;

                case 2:
                    if (aDoorNum == 1)
                        return 1;
                    else if (aDoorNum == 2)
                        return 3;
                    break;

                case 3:
                    if (aDoorNum == 2)
                        return 2;
                    else if (aDoorNum == 3)
                        return 4;
                    break;

                case 4:
                    if (aDoorNum == 3)
                        return 3;
                    else if (aDoorNum == 4)
                        return 5;
                    else if (aDoorNum == 5)
                        return 6;
                    break;

                case 5:
                    if (aDoorNum == 4)
                        return 4;
                    break;

                case 6:
                    if (aDoorNum == 5)
                        return 4;
                    break;
            }
            return 2;
        }

        public float TestForOpenDoors(int aDoorNumber)
        {
            foreach(PrisonModel lModel in phibesModel)
            {
                float lTemp = lModel.CheckOpenDoors(aDoorNumber);
                if (lTemp > 0)
                    return lTemp;
            }
            return -1;
        }
    }
    
}
